use std::mem;

use crate::{
    app_state::AppState,
    errors::error::AppError,
    middlewares::{hash_password, verify_password},
    models::user::{User, UserSaveDTO},
};
pub struct UserService;

impl UserService {
    pub async fn find_by_email(
        email: &str,
        password: &str,
        state: AppState,
    ) -> Result<Option<User>, AppError> {
        let pool = &state.pool;
        let user_option = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = $1")
            .bind(email)
            .fetch_optional(pool)
            .await?;
        match user_option {
            Some(mut user) => {
                let password_hash = mem::take(&mut user.password_hash);
                let is_valid = verify_password(password, &password_hash)?;
                if is_valid {
                    return Ok(Some(user));
                } else {
                    return Ok(None);
                }
            }
            None => {
                return Ok(None);
            }
        }
    }
    pub async fn save(dto: UserSaveDTO, state: AppState) -> Result<User, AppError> {
        let pool = &state.pool;
        if sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = $1")
            .bind(&dto.email)
            .fetch_optional(pool)
            .await?
            .is_some()
        {
            return Err(AppError::UserAlreadyExists);
        };
        // Hash the password (assuming you have a function to hash passwords)
        let password_hash = hash_password(&dto.password)?;
        let user = sqlx::query_as::<_, User>(
            r#"
            INSERT INTO users (username, email, password_hash)
            VALUES ($1, $2, $3)
            RETURNING id, username, email, password_hash
        "#,
        )
        .bind(&dto.username)
        .bind(&dto.email)
        .bind(password_hash)
        .fetch_one(pool)
        .await?;
        Ok(user)
    }
    pub async fn find_by_id(id: u64, state: &AppState) -> Result<User, AppError> {
        let pool = &state.pool;
        let user = sqlx::query_as::<_, User>(
            r#"
            SELECT id, username, email, password_hash
            FROM users
            WHERE id =$1 
        "#,
        )
        .bind(id as i64)
        .fetch_optional(pool)
        .await?;
        match user {
            Some(user) => Ok(user),
            None => Err(AppError::UserNotFound("User not found".to_string())),
        }
    }
}
